#pragma once
#include "message.hpp"
#include "helper.hpp"
#include "../log_client/client.hpp"
#include "timer.hpp"
#include <string>
#include <fstream>
#include <sstream>
#include <atomic>
#include <iomanip>
#include <cstring>

namespace ns_log
{
    using namespace ns_helper;
    enum class LoggerType;
    enum class AsyncType;

    class FSLogSink
    {
    public:
        using ptr = std::shared_ptr<FSLogSink>;
        virtual ~FSLogSink() {}
        virtual void log_fs(const char *data, size_t len) = 0;
    };

    class StdoutSink : public FSLogSink
    {
    public:
        virtual void log_fs(const char *data, size_t len)
        {
            // 注意，不能用流插入直接写，因为写的不一定是字符串，而流插入写字符串只有遇到'\0'才会终止
            // 因此，我们要用std::cout.write(data,len);
            std::cout.write(data, len);
        }
    };

    class FileSink : public FSLogSink
    {
    public:
        FileSink(const std::string &filename)
            : _filename(filename), _file_elem(std::make_shared<FileElem>())
        {
            // 1. 创建filename所在目录
            FileHelper::createDir(FileHelper::getPath(_filename));
            // 2. 打开文件(二进制 + 追加写)
            _file_elem->_ofs.open(_filename, std::ios::binary | std::ios::app);
            if (!_file_elem->_ofs.is_open())
            {
                std::cout << "FileSink 打开文件失败, 文件名: " << _filename << "\n";
                abort();
            }
            // 3. 设置定时任务
            FileTimerHelper::getInstance().set_timer(_file_elem);
        }
        ~FileSink()
        {
            // 1. 删除定时任务
            FileTimerHelper::getInstance().cancel_timer(_file_elem);
            // 2. 防止fd泄露
            if (_file_elem->_ofs.is_open())
                _file_elem->_ofs.close();
        }
        virtual void log_fs(const char *data, size_t len)
        {
            std::unique_lock<std::mutex> ulock(_file_elem->_mutex);
            _file_elem->_ofs.write(data, len);
            if (!_file_elem->_ofs.good())
            {
                std::cout << "FileSink 写入文件有问题, 文件名: " << _filename << "\n";
                abort();
            }
        }

    private:
        std::string _filename;
        FileElem::ptr _file_elem;
    };

    class RollSinkBySize : public FSLogSink
    {
    public:
        RollSinkBySize(const std::string &basename, size_t max_size)
            : _basename(basename), _file_elem(std::make_shared<FileElem>()), _max_size(max_size), _cur_size(0), _name_count(0)
        {
            check();
        }
        ~RollSinkBySize()
        {
            // 取消定时任务
            FileTimerHelper::getInstance().cancel_timer(_file_elem);
            // 防止fd泄露
            if (_file_elem->_ofs.is_open())
                _file_elem->_ofs.close();
        }
        virtual void log_fs(const char *data, size_t len)
        {
            check();
            std::unique_lock<std::mutex> ulock(_file_elem->_mutex);
            _file_elem->_ofs.write(data, len);
            if (!_file_elem->_ofs.good())
            {
                std::cout << "RollSinkBySize 写入文件有问题, 基础文件名: " << _basename << "\n";
                abort();
            }
            _cur_size += len;
        }

    private:
        void check()
        {
            if (!_file_elem->_ofs.is_open() || _cur_size >= _max_size)
            {
                // 0. 关闭旧文件
                if (_file_elem->_ofs.is_open())
                {
                    // 取消定时任务
                    FileTimerHelper::getInstance().cancel_timer(_file_elem);
                    _file_elem->_ofs.close();// 因为已经取消了定时任务，所以这里的close操作线程安全
                }
                // 1. 拿到新的文件名
                std::string newFile = createNewFile();
                // 2. 创建该文件
                FileHelper::createDir(FileHelper::getPath(newFile));
                // 3. 打开该文件
                _file_elem->_ofs.open(newFile, std::ios::app | std::ios::binary);
                if (!_file_elem->_ofs.is_open())
                {
                    std::cout << "RollSinkBySize 打开文件有问题, 基础文件名: " << _basename << ", 该文件名: " << newFile << "\n";
                    abort();
                }
                // 4. 清空cur_size
                _cur_size = 0;
                // 6. 添加定时任务
                FileTimerHelper::getInstance().set_timer(_file_elem);
            }
        }
        std::string createNewFile()
        {
            std::ostringstream oss;
            // 基础文件名 + 时间戳(年月日时分秒)+ 序号 + ".log"
            time_t now = DateHelper::now();
            struct tm *tm = localtime(&now);
            oss << _basename << tm->tm_year + 1900 << std::setw(2) << std::setfill('0') << tm->tm_mon + 1 << std::setw(2) << std::setfill('0') << tm->tm_mday
                << std::setw(2) << std::setfill('0') << tm->tm_hour
                << std::setw(2) << std::setfill('0') << tm->tm_min << std::setw(2) << std::setfill('0')
                << tm->tm_sec << "-" << _name_count++ << ".log";
            return oss.str();
        }

        std::string _basename;
        FileElem::ptr _file_elem;
        size_t _name_count;
        size_t _max_size;
        size_t _cur_size;
    };

    class DBLogSink
    {
    public:
        using ptr = std::shared_ptr<DBLogSink>;
        virtual ~DBLogSink() {}
        virtual void log_db(const LogMessage::ptr &message) = 0;
        // 开启事务 ->  多次insert -> 关闭事务
        virtual void log_db(const std::vector<LogMessage::ptr> &message_vec, size_t sz) = 0;
    };

    class SqliteSink : public DBLogSink
    {
    public:
        SqliteSink(const std::string &filename)
            : _helper(filename)
        {
            // 1. 创建文件所在目录
            if (!FileHelper::createDir(FileHelper::getPath(filename)))
            {
                std::cout << "SQLite3数据库文件所在目录创建失败\n";
                abort();
            }
            // 2. 创建文件
            if (!FileHelper::createFile(filename))
            {
                std::cout << "SQLite3数据库文件创建失败\n";
                abort();
            }
            // 3. 打开数据库文件
            if (!_helper.open())
            {
                std::cout << "SQLite3数据库文件打开失败\n";
                abort();
            }
            // 4. 建表
            if (!createTable())
            {
                std::cout << "SQLite3数据库的建表失败\n";
                abort();
            }
        }

        virtual void log_db(const LogMessage::ptr &message)
        {
            std::ostringstream insert_sql;
            insert_sql << "insert into LogData(level,file,line,logger_name,thread_id,body) values(";
            insert_sql << "'" << LogLevel::LogLevel_Name(message->_level) << "',";
            insert_sql << "'" << message->_file << "',";
            insert_sql << message->_line << ",";
            insert_sql << "'" << message->_logger_name << "',";
            insert_sql << message->_thread_id << ",";
            insert_sql << "'" << message->_body << "');";
            if (!_helper.exec(insert_sql.str(), nullptr, nullptr))
            {
                std::cout << "插入数据失败，表名：LogData\n";
            }
        }

        // 开启事务 ->  多次insert -> 关闭事务
        virtual void log_db(const std::vector<LogMessage::ptr> &message_vec, size_t sz)
        {
            if (!_helper.begin())
            {
                std::cout << "SQLlite3：开启事务失败\n";
                return;
            }
            for (int i = 0; i < sz; i++)
            {
                log_db(message_vec[i]);
            }
            if (!_helper.commit())
            {
                std::cout << "SQLlite3：提交事务失败\n";
                return;
            }
        }

    private:
        bool createTable()
        {
            // 指明autoincrement必须要用integer，不能用int
            // SQLite3的current_timestamp是格林威治时间，需要加上8小时改为北京时间
            static std::string create_sql = R"(create table if not exists LogData(
                                                id integer primary key autoincrement,
                                                date text default(datetime(current_timestamp,'+8 hours')),
                                                level varchar(10),
                                                file varchar(32),
                                                line int,
                                                logger_name varchar(32) not null,
                                                thread_id int,
                                                body text
                                            );)";
            if (!_helper.exec(create_sql, nullptr, nullptr))
            {
                std::cout << "创建SQLite3数据库表失败，表名：LogData\n";
                return false;
            }
            return true;
        }

        SqliteHelper _helper;
    };

    class MySQLSink : public DBLogSink
    {
    public:
        MySQLSink(const std::string &conf_file)
            : _helper(conf_file)
        {
            // 打开数据库文件
            if (!_helper.open())
            {
                std::cout << "MySQL数据库文件打开失败\n";
                abort();
            }
            if (!createTable())
            {
                std::cout << "MySQL数据库的建表失败\n";
                abort();
            }
        }

        virtual void log_db(const LogMessage::ptr &message)
        {
            std::ostringstream insert_sql;
            insert_sql << "insert into LogData(level,file,line,logger_name,thread_id,body) values(";
            insert_sql << "'" << LogLevel::LogLevel_Name(message->_level) << "',";
            insert_sql << "'" << message->_file << "',";
            insert_sql << message->_line << ",";
            insert_sql << "'" << message->_logger_name << "',";
            insert_sql << message->_thread_id << ",";
            insert_sql << "'" << message->_body << "');";
            if (!_helper.exec(insert_sql.str()))
            {
                std::cout << "插入数据失败，表名：LogData\n";
            }
        }

        // 开启事务 ->  多次insert -> 关闭事务
        virtual void log_db(const std::vector<LogMessage::ptr> &message_vec, size_t sz)
        {
            if (!_helper.begin())
            {
                std::cout << "MySQL：开启事务失败\n";
                return;
            }
            for (int i = 0; i < sz; i++)
            {
                log_db(message_vec[i]);
            }
            if (!_helper.commit())
            {
                std::cout << "MySQL：提交事务失败\n";
                return;
            }
        }

    private:
        bool createTable()
        {
            std::string create_sql = R"(create table if not exists LogData(
                                                id int primary key auto_increment,
                                                date datetime default current_timestamp,
                                                level varchar(10),
                                                file varchar(32),
                                                line int,
                                                logger_name varchar(32) not null,
                                                thread_id bigint,
                                                body text
                                            );)";
            if (!_helper.exec(create_sql))
            {
                std::cout << "创建MySQL数据库表失败，表名：LogData\n";
                return false;
            }
            return true;
        }

        MySQLHelper _helper;
    };

    // （懒汉）单例
    // 负责维护（LogClient）连接, 所有RemoteSink对象当中的_client都要从RemoteSinkManager当中获取
    /*
        主要是,这种文件如果可以灵活上传,那么随便一个低权限的用户就可以搞事情了
        这不行,这种程度的配置文件一般都得由root创建并进行权限控制
    */
    class RemoteSinkManager
    {
    public:
        static RemoteSinkManager &getInstance()
        {
            static RemoteSinkManager remote_sink_manager;
            return remote_sink_manager;
        }

        LogClient::ptr getClient()
        {
            return _client;
        }

    private:
        RemoteSinkManager()
        {
            init();
        }
        ~RemoteSinkManager() {}
        RemoteSinkManager(const RemoteSinkManager &) = delete;
        const RemoteSinkManager &operator=(const RemoteSinkManager &) = delete;
        void init()
        {
            std::ifstream ifs(remote_conf_file);
            if (!ifs.is_open())
            {
                std::cout << "ifs.is_open())\n";
                abort();
            }
            std::string server_ip;
            int server_port = -1;
            std::string connection_id;
            std::string line;
            while (getline(ifs, line))
            {
                size_t pos = line.find(":");
                if (pos != std::string::npos)
                {
                    std::string prev = line.substr(0, pos);
                    if (prev == "server_ip")
                        server_ip = line.substr(pos + 1);
                    else if (prev == "server_port")
                        server_port = std::stoi(line.substr(pos + 1));
                    else if (prev == "connection_id")
                        connection_id = line.substr(pos + 1);
                }
            }
            ifs.close();
            std::cout << server_ip << " " << server_port << " " << connection_id << "\n";
            _client = std::make_shared<LogClient>(server_ip, server_port);
            std::cout << "_client建立成功\n";
            if (!_client->connect(connection_id))
            {
                std::cout << "服务器创建connection对象失败\n";
                abort();
            }
        }
        LogClient::ptr _client;
    };

    class RemoteSink
    {
    public:
        using ptr = std::shared_ptr<RemoteSink>;

        RemoteSink(const std::string &logger_name, LoggerType logger_type,
                   LogLevel::value limit_level, const std::string &pattern, AsyncType async_type, const std::vector<std::vector<std::string>> &sink_vec)
            : _client(RemoteSinkManager::getInstance().getClient())
        {
            if (_client.get() == nullptr)
            {
                std::cout << "_client.get()==nullptr\n";
                abort();
            }
            _client->declareLogger(logger_name, logger_type, limit_level, pattern, async_type, sink_vec);
        }

        void log_web(LogMessage::ptr message)
        {
            _client->web_log({message});
        }

        void log_web(const std::vector<LogMessage::ptr> &message)
        {
            _client->web_log(message);
        }

    private:
        LogClient::ptr _client;
    };

    class LogSinkFactory
    {
    public:
        // 函数模板
        template <class SinkType, class... Args>
        static FSLogSink::ptr create_fs(Args &&...args)
        {
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }

        template <class SinkType, class... Args>
        static DBLogSink::ptr create_db(Args &&...args)
        {
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };

}
